﻿using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text;

namespace LoongEgg.Check
{ 
    /// <summary>
    /// Algorithm of Cyclic Redundancy Check
    /// </summary>
    public partial class CrcAlgorithm
    {
        private const string ALGORITHMS_DIRECTORY = @"default/";
        private const string ALGORITHMS_LIST_FILE_NAME = @"crc_algorithms.txt";

        private readonly UInt64 _Mask;

        /// <summary>
        /// 算法的名称
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// 位宽
        /// </summary>
        /// <remarks>
        /// 没有考虑开头的1, 实际上的16位CRC生成多项式为17位, 但是Width=16
        /// </remarks>
        public int Width { get; }

        /// <summary>
        /// 字节数
        /// </summary>
        public int Size { get; }

        /// <summary>
        /// 速算表
        /// </summary>
        public UInt64[] Table => _Table.ToArray();
        private readonly UInt64[] _Table = new UInt64[256];

        /// <summary>
        /// 生成多项式
        /// </summary>
        public UInt64 Poly { get; }

        /// <summary>
        /// 初始值, intial value
        /// </summary>
        public UInt64 Init { get; }

        /// <summary>
        /// 输入的数据镜像翻转? Is reflection input?
        /// </summary>
        public bool IsRefInp { get; }

        /// <summary>
        /// 输出的数据镜像翻转? Is reflection out?
        /// </summary>
        public bool IsRefOut { get; }

        /// <summary>
        /// 结果异或的值, final xor out value
        /// </summary>
        public UInt64 XorOut { get; }

        /// <summary>
        /// 计算<see cref="Standard.Validate"/>后应该得到的Crc, result should get when caculate from "123456789"
        /// </summary>
        public UInt64 Check { get; }

        /// <summary>
        /// 验算通过
        /// </summary>
        public bool IsChecked => Check == ComputeCrc(Standard.Validate, Standard.Validate.Length);

        /// <summary>
        /// instance of <see cref="CrcAlgorithm"/>
        /// </summary>
        /// <param name="name">算法的名称</param>
        /// <param name="width">位宽(没有考虑开头的1, 实际上的16位CRC生成多项式为17位, 但是Width=16)</param>
        /// <param name="poly">生成多项式, generator polynomial, for example CRC-16/X-25 is X^16+X^12+X^5+X^0 by ignore the first bit, input 0x1021 here</param>
        /// <param name="init">初始值, initial value</param>
        /// <param name="isRefInp">输入的数据镜像翻转?Is reflection input?</param>
        /// <param name="isRefOut">输出的数据镜像翻转?Is reflection out?</param>
        /// <param name="xorOut">结果异或的值</param>
        /// <param name="check">计算ASCII "123456789"后应该得到的验算值, the result should get when caculated from ASCII "123456789"</param>
        /// <exception cref="NotImplementedException"></exception>
        /// <remarks>
        /// [reflect]: 1100-1110-1010 ->  0101-0111-0011
         /// </remarks>
        public CrcAlgorithm(
            string name,
            uint width,
            UInt64 poly,
            UInt64 init,
            bool isRefInp,
            bool isRefOut,
            UInt64 xorOut,
            UInt64 check)
        {
            Name = name;
            Width = (int)width;
            Size = (int)Math.Ceiling(Width / 8.0);
            Poly =  poly;
            IsRefInp = isRefInp;
            IsRefOut = isRefOut;
            XorOut = xorOut;

            Init = IsRefInp ? init.ReflectBits((int)Width) : init;

            _Mask = UInt64.MaxValue >> (64 - Width);
            CreatTable();
            Check = (UInt64)check;
        }

        /// <summary>
        /// 校验
        /// </summary>
        /// <param name="data">data only not contains check code</param>
        /// <param name="checkCode">check code</param>
        /// <returns></returns>
        public bool Validated(byte[] data, byte[] checkCode) => Validated(data, checkCode.ToUInt64());

        /// <summary>
        /// 校验
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="checkCode">Crc码</param>
        /// <returns>[true]: 校验通过; [false]: 校验失败.</returns>
        public bool Validated(byte[] data, UInt64 checkCode)
        {
            var actual = ComputeCrc(data, data.Length);
            var expected = checkCode;
            return actual == expected;
        }

        /// <summary>
        /// 校验
        /// </summary>
        /// <param name="buff">数据流, {head}{data(start from index=offset, with length)}{crc}{tail or nothing}</param>
        /// <param name="length">要计算CRC的数据长度(length of data)</param>
        /// <param name="offset">要计算CRC的数据起点(start index of data)</param>
        /// <returns>[ true ]:Validating right; [ false ]:Validating fail.</returns>
        public bool Validated(byte[] buff, int length, int offset = 0)
        {
            if (buff == null)
                throw new ArgumentNullException($"{nameof(buff)}");
            if (buff.Length < offset + length + Size)
            {
                throw new IndexOutOfRangeException($"length of buff is {buff.Length}, but start index of data is {offset}, length of data to check is{length}, and crc size int data is {Size}. offset + length + size > buff.Length");
            }
            var expected = PickCrc(buff, offset + length);
            var actual = ComputeCrc(buff, length, offset);
            return expected == actual;
        }

        /// <summary>
        /// pick crc from buff
        /// </summary>
        /// <param name="buff">buff contains the crc in the tail</param>
        /// <param name="startIndex">crc start index</param>
        /// <returns>crc</returns>
        /// <remarks>
        /// just input the start index, length is recognised from Polynomical Width / 8
        /// </remarks>
        public UInt64 PickCrc(byte[] buff, int startIndex)
        {
            UInt64 ret = 0;
            int i = Size - 1;
            while (i > 0)
            {
                ret |= buff[startIndex + i];
                ret <<= 8;
                i -= 1;
            }

            ret |= buff[startIndex];
            return ret;
        }

        /// <summary>
        /// 计算CRC值
        /// </summary>
        /// <param name="data">原始数据</param>
        /// <param name="length">计算的长度</param>
        /// <param name="offset">计算的起点</param>
        /// <returns>crc</returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="IndexOutOfRangeException"></exception>
        public UInt64 ComputeCrc(byte[] data, int length, int offset = 0)
        {
            if (data == null || data.Length <= 0)
                throw new ArgumentNullException($"{nameof(data)}");
            if (length > data.Length - offset)
                throw new IndexOutOfRangeException(
                    $"Max index={data.Length}, " +
                    $"offset={offset}, " +
                    $"but compute length={length}, " +
                    $"offset + length > max_index");

            UInt64 crc = Init;

            if (IsRefOut == true)
            {
                for (int i = offset; i < offset + length; i++)
                {
                    crc = _Table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
                    crc &= _Mask;
                }
            }
            else
            {
                int toRight = (Width - 8);
                toRight = toRight < 0 ? 0 : toRight;
                for (int i = offset; i < offset + length; i++)
                {
                    crc = _Table[((crc >> toRight) ^ data[i]) & 0xFF] ^ (crc << 8);
                    crc &= _Mask;
                }
            }
            crc ^= XorOut;
            return crc;
        }

        private void CreatTable()
        {
            for (int i = 0; i < 256; i++)
                _Table[i] = GetTableEntry(i);
        }

        private UInt64 GetTableEntry(int index)
        {
            UInt64 r = (UInt64)index;
            if (IsRefInp == true)
                r = r.ReflectBits(Width);
            else if (Width > 8)
                r <<= (Width - 8);

            UInt64 lastBit = ((UInt64)1 << (Width - 1));
            for (int i = 0; i < 8; i++)
            {
                if ((r & lastBit) != 0)
                    r = (r << 1) ^ Poly;
                else
                    r <<= 1;
            }

            if (IsRefInp == true)
                r = r.ReflectBits(Width);

            return r & _Mask;
        }
    }
}
